package com.tcs.utils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;

import com.tcs.tsrm.beans.ClusteringThresholds;

public class GetNontrivialSubs {
	public static String clusterIndexesPath;
	public static String originalMatrixPath;
	public static String outputPath; // Output/coin-bst/EOT20
	public static String nonTrivialMatrixPath;
	private static HashMap<Integer, Integer> originalToNonTrivial = new HashMap<Integer, Integer>();
	double R = 0;
	private static int window = 0;
	private static int totalSubsequences = 0;
	private static int remainingSubsequences = 0;

	public static void main(String[] args) {
		String inputSpecFile = null;
		if (args.length < 1) {
			System.err
			.println("Not enough Arguments Given, try passing sample.properties as input");
		} else {
			inputSpecFile = args[0];
		}
		Properties prop = new Properties();
		try {
			prop.load(new FileInputStream("input-spec/" + inputSpecFile
					+ ".properties"));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		clusterIndexesPath = prop.getProperty("clusterIndexesPath");
		originalMatrixPath = prop.getProperty("originalMatrixPath");
		outputPath = prop.getProperty("outputPath");
		nonTrivialMatrixPath = prop.getProperty("nonTrivialMatrixPath");

		GetNontrivialSubs runner = new GetNontrivialSubs();
		ClusteringThresholds thresholds = null;
		runner.setDefaultThresholds(thresholds);
		runner.computeNonTrivialSubs(thresholds);
		System.out.println("Trivial Match Removal Done...");

	}

	private void setDefaultThresholds(ClusteringThresholds thresholds) {
		String currentDirectory = new File("").getAbsolutePath();
		thresholds.setPrintOrgSubsClusters(true);
		thresholds.setPrintCentroids(true);
		thresholds.setPrintClusteringStats(true);
		thresholds.setPrintClusters(true);
		thresholds.setPrintClustersRedDim(true);
		thresholds.setPrintNonTrivialMatrix(true);
		thresholds.setPrintOriginalMatrix(true);
		thresholds.setSplittingLimits("-1.15,-0.67,-0.32,0.0,0.32,0.67,1.15");
		thresholds.setB(100);
		thresholds.setDataInputPath(currentDirectory + "/input-data/");

	}

	public void computeNonTrivialSubs(ClusteringThresholds thresholds) {
		try {
			R = thresholds.getCoinRadius();
			window = thresholds.getMotifWidth();
			HashMap<Integer, ArrayList<Integer>> allClusters = loadClusters();
			HashMap<Integer, ArrayList<Double>> originalMatrix = loadOriginalMatrix();
			HashMap<Integer, ArrayList<Double>> nonTrivialMatrix = loadNonTrivialMatrix();
			long trivialMatchRemovalStart = System.currentTimeMillis();
			HashMap<Integer, ArrayList<Integer>> clustersWithNonTrivialSubs = computeNonTrivialsubs(
					allClusters, originalMatrix);
			long trivialMatchRemovalStop = System.currentTimeMillis();
			long trivialMatchRemovalTime = trivialMatchRemovalStop
					- trivialMatchRemovalStart;
			System.out.println("Trivial Match Removal Time->"
					+ (double) trivialMatchRemovalTime / 1000 + " seconds");

			HashMap<Integer, ArrayList<Double>> centroids = getCentroids(
					clustersWithNonTrivialSubs, nonTrivialMatrix);

			printResults(clustersWithNonTrivialSubs, originalMatrix,
					nonTrivialMatrix, centroids,thresholds);
			printStats(totalSubsequences, remainingSubsequences,thresholds);

		} catch (NumberFormatException e) {
			e.printStackTrace();
		}

	}

	private void printStats(int totalSubs, int remainingSubs,ClusteringThresholds thresholds) {
		if(thresholds.isDebug()==true)
		{
			File fileOriginal = new File(outputPath + "/" + "NonTrivial" + "/"
					+ "SubsStats.txt");
			fileOriginal.getParentFile().mkdirs();
			PrintWriter statOut2;
			try {
				statOut2 = new PrintWriter(new BufferedWriter(new FileWriter(
						fileOriginal, false)));
				statOut2.write("Total subsequences, Remaining Subsequences\n");
				statOut2.write(totalSubs + "," + remainingSubs);
				statOut2.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private HashMap<Integer, ArrayList<Double>> getCentroids(
			HashMap<Integer, ArrayList<Integer>> clustersWithNonTrivialSubs,
			HashMap<Integer, ArrayList<Double>> nonTrivialMatrix) {
		HashMap<Integer, ArrayList<Double>> centroids = new HashMap<Integer, ArrayList<Double>>();

		for (Integer key : clustersWithNonTrivialSubs.keySet()) {
			ArrayList<Integer> subsIndex = clustersWithNonTrivialSubs.get(key);
			ArrayList<ArrayList<Double>> nonTrivialSubs = new ArrayList<ArrayList<Double>>();
			for (Integer eachIndex : subsIndex) {
				int nonTrivialIndex = originalToNonTrivial.get(eachIndex);
				nonTrivialSubs.add(nonTrivialMatrix.get(nonTrivialIndex));
			}
			ArrayList<Double> centroid = calculateCentroid(nonTrivialSubs);
			centroids.put(key, centroid);
		}

		return centroids;
	}

	private ArrayList<Double> calculateCentroid(
			ArrayList<ArrayList<Double>> nonTrivialSubs) {
		double[] sum = new double[nonTrivialSubs.get(0).size()];
		double noOfPoint = nonTrivialSubs.size();
		for (ArrayList<Double> eachSubs : nonTrivialSubs) {
			for (int i = 0; i < eachSubs.size(); i++) {
				sum[i] += eachSubs.get(i);
			}
		}

		ArrayList<Double> centroid = new ArrayList<Double>();
		for (int i = 0; i < sum.length; i++) {
			centroid.add(sum[i] / noOfPoint);
		}
		return centroid;
	}

	private HashMap<Integer, ArrayList<Double>> loadNonTrivialMatrix()
			throws NumberFormatException {
		HashMap<Integer, ArrayList<Double>> nonTrivialMatrix = new HashMap<Integer, ArrayList<Double>>();
		try {
			FileInputStream fstream = new FileInputStream(nonTrivialMatrixPath);
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			while ((strLine = br.readLine()) != null) {
				String[] data = strLine.split(",");
				double nonTrivialMatIndex = Double.valueOf(data[0]);
				double originalMatIndex = Double.valueOf(data[1]);
				originalToNonTrivial.put((int) originalMatIndex,
						(int) nonTrivialMatIndex);
				ArrayList<Double> aRow = new ArrayList<Double>();
				for (int i = 2; i < data.length; i++) {
					aRow.add(Double.valueOf(data[i]));
				}
				nonTrivialMatrix.put((int) nonTrivialMatIndex, aRow);
			}
			br.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return nonTrivialMatrix;
	}

	private void printResults(
			HashMap<Integer, ArrayList<Integer>> clustersWithNonTrivialSubs,
			HashMap<Integer, ArrayList<Double>> originalMatrix,
			HashMap<Integer, ArrayList<Double>> nonTrivialMatrix,
			HashMap<Integer, ArrayList<Double>> centroids,ClusteringThresholds thresholds) {
		PrintingClusters pc = new PrintingClusters();
		pc.printNonTrivialSubs(clustersWithNonTrivialSubs, originalMatrix,
				outputPath,thresholds);
		pc.printNonTrivialClusteringIndex(clustersWithNonTrivialSubs,
				outputPath);
		pc.printNonTrivialRedSubs(clustersWithNonTrivialSubs,
				originalToNonTrivial, nonTrivialMatrix, outputPath,thresholds);
		pc.printNonTrivialCentorids(centroids, outputPath,thresholds);

	}

	private HashMap<Integer, ArrayList<Integer>> computeNonTrivialsubs(
			HashMap<Integer, ArrayList<Integer>> allClusters,
			HashMap<Integer, ArrayList<Double>> originalMatrix) {
		HashMap<Integer, ArrayList<Integer>> clusterWithNonTrivialSubs = new HashMap<Integer, ArrayList<Integer>>();
		for (Integer key : allClusters.keySet()) {
			ArrayList<Integer> subsIndexes = allClusters.get(key);

			ArrayList<Integer> nonTrivialIndexes = getNonTrivialIndexes(
					subsIndexes, originalMatrix);
			nonTrivialIndexes = delOverlaps(subsIndexes, window);
			clusterWithNonTrivialSubs.put(key, nonTrivialIndexes);
			totalSubsequences += subsIndexes.size();
			remainingSubsequences += nonTrivialIndexes.size();
		}
		return clusterWithNonTrivialSubs;
	}

	private ArrayList<Integer> delOverlaps(ArrayList<Integer> subsIndexes, int w) {
		ArrayList<Integer> finalIndexes = new ArrayList<Integer>();
		finalIndexes.add(subsIndexes.get(0));
		boolean toIterate = true;
		int goodSubsIndex = 0;
		while (toIterate) {
			int i = goodSubsIndex;
			for (int j = goodSubsIndex; j < subsIndexes.size(); j++) {
				if (j == i) {
					continue;
				}
				int d = subsIndexes.get(j).intValue()
						- subsIndexes.get(i).intValue();
				if (d <= w) {
					toIterate = false;
					continue;
				}
				finalIndexes.add(subsIndexes.get(j));
				goodSubsIndex = j;
				toIterate = true;
				break;
			}

			if (goodSubsIndex >= (subsIndexes.size() - 1)) {
				toIterate = false;
			}
		}

		return finalIndexes;
	}

	private ArrayList<Integer> getNonTrivialIndexes(
			ArrayList<Integer> subsIndexes,
			HashMap<Integer, ArrayList<Double>> originalMatrix) {
		Collections.sort(subsIndexes);
		ArrayList<Integer> nonTrivialSubsIndexes = new ArrayList<Integer>();
		nonTrivialSubsIndexes.add(subsIndexes.get(0));
		boolean flag = false;
		int lastDiscNonTrivialIndex = subsIndexes.get(0);
		ArrayList<Double> centroidValues = calculateCentroidOfSubsIndex(
				subsIndexes, originalMatrix);
		for (int i = 1; i < subsIndexes.size(); i++) {
			if ((subsIndexes.get(i) - lastDiscNonTrivialIndex) > 1) {
				flag = checkForPointsInRange(lastDiscNonTrivialIndex,
						subsIndexes.get(i), originalMatrix, centroidValues);
				if (flag) {
					lastDiscNonTrivialIndex = subsIndexes.get(i);
					nonTrivialSubsIndexes.add(subsIndexes.get(i));
					flag = false;
				}
			}
		}

		return nonTrivialSubsIndexes;
	}

	private ArrayList<Double> calculateCentroidOfSubsIndex(
			ArrayList<Integer> subsIndexes,
			HashMap<Integer, ArrayList<Double>> originalMatrix) {
		// ///now mine
		ArrayList<ArrayList<Double>> calculateCentroid = new ArrayList<ArrayList<Double>>();
		for (int i = 0; i < subsIndexes.size(); i++) {
			calculateCentroid.add(originalMatrix.get(subsIndexes.get(i)));
		}
		return calculateNewCentroid(calculateCentroid);

	}

	private ArrayList<Double> calculateNewCentroid(
			ArrayList<ArrayList<Double>> clusterSubs) {
		double[] sum = new double[clusterSubs.get(0).size()];
		double noOfPoint = clusterSubs.size();
		for (ArrayList<Double> eachSubs : clusterSubs) {
			for (int i = 0; i < eachSubs.size(); i++) {
				sum[i] += eachSubs.get(i);
			}
		}

		ArrayList<Double> centroid = new ArrayList<Double>();
		for (int i = 0; i < sum.length; i++) {
			centroid.add(sum[i] / noOfPoint);
		}
		return centroid;
	}

	private boolean checkForPointsInRange(Integer fromIndex, Integer uptoIndex,
			HashMap<Integer, ArrayList<Double>> originalMatrix,
			ArrayList<Double> centroids) {

		for (int i = fromIndex; i < uptoIndex; i++) {
			for (int j = i + 1; j < uptoIndex; j++) {
				if ((computeDist(originalMatrix.get(j), centroids) >= R)
						|| ((computeDist(originalMatrix.get(i),
								originalMatrix.get(j)) >= R) && (computeDist(
										originalMatrix.get(j),
										originalMatrix.get(uptoIndex)) >= R))) {
					return true;

				}

			}
		}
		return false;

	}

	private double computeDist(ArrayList<Double> arrayList,
			ArrayList<Double> arrayList2) {
		double sum = 0;
		for (int i = 0; i < arrayList.size(); i++) {
			sum += Math.pow(arrayList.get(i) - arrayList2.get(i), 2);
		}
		return Math.sqrt(sum);
	}

	private HashMap<Integer, ArrayList<Double>> loadOriginalMatrix()
			throws NumberFormatException {
		HashMap<Integer, ArrayList<Double>> originalMatrix = new HashMap<Integer, ArrayList<Double>>();
		FileInputStream fstream;
		try {
			fstream = new FileInputStream(originalMatrixPath);

			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			while ((strLine = br.readLine()) != null) {
				String[] data = strLine.split(",");
				double rowId = Double.valueOf(data[0]);
				ArrayList<Double> aRow = new ArrayList<Double>();
				for (int i = 1; i < data.length; i++) {
					aRow.add(Double.valueOf(data[i]));
				}
				originalMatrix.put((int) rowId, aRow);
			}
			br.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return originalMatrix;

	}

	private HashMap<Integer, ArrayList<Integer>> loadClusters()
			throws NumberFormatException {
		HashMap<Integer, ArrayList<Integer>> clusters = new HashMap<Integer, ArrayList<Integer>>();
		FileInputStream fstream;
		try {
			fstream = new FileInputStream(clusterIndexesPath);
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			while ((strLine = br.readLine()) != null) {
				String[] data = strLine.split(",");
				int clusterId = Integer.valueOf(data[0]);
				ArrayList<Integer> aCluster = new ArrayList<Integer>();
				for (int i = 1; i < data.length; i++) {
					aCluster.add(Integer.valueOf(data[i]));
				}
				clusters.put(clusterId, aCluster);
			}
			br.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return clusters;
	}

}
